package com.jilujia.framework.domain;

import java.io.Serializable;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import com.jilujia.framework.audit.PoEntityAuditorAware;

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public abstract class AbstractAuditPoEntityManager<ID extends Serializable, PO extends AuditablePoEntity<ID>, Repository extends JpaBusinessRepository<ID, PO>, T>
		extends AbstractPoEntityManager<ID, PO, Repository> {

	private static final Logger LOG = LoggerFactory.getLogger(AbstractAuditPoEntityManager.class);

	private PoEntityAuditorAware<T> auditorAware;

	@Transactional
	public void save() {
		Assert.notNull(this.getInstance(), "Required instance must not be null");
		this.preSave();

		if (this.getInstance().getId() == null) {
			touchForCreate(this.getInstance());
		}

		touchForUpdate(this.getInstance());
		this.setInstance(this.getRepository().save(this.getInstance()));

		this.postSave();

	}

	@Autowired
	public void setAuditorAware(PoEntityAuditorAware<T> auditorAware) {
		this.auditorAware = auditorAware;
	}

	public PoEntityAuditorAware<T> getAuditorAware() {
		return auditorAware;
	}

	public void touchForCreate(Object target) {

		touch(target, true);
	}

	public void touchForUpdate(Object target) {

		touch(target, false);
	}

	private void touch(Object target, boolean isNew) {

		if (!(target instanceof AuditablePoEntity)) {
			return;
		}

		@SuppressWarnings("unchecked")
		AuditablePoEntity<ID> auditable = (AuditablePoEntity<ID>) target;

		T auditor = touchAuditor(auditable, isNew);
		DateTime now = touchDate(auditable, isNew);

		Object defaultedNow = now == null ? "not set" : now;
		Object defaultedAuditor = auditor == null ? "unknown" : auditor;

		LOG.debug("Touched {} - Last modification at {} by {}", new Object[] { auditable, defaultedNow,
				defaultedAuditor });
	}

	/**
	 * Sets modifying and creating auditioner. Creating auditioner is only set on new auditables.
	 * 
	 * @param auditable
	 * @return
	 */
	private T touchAuditor(final AuditablePoEntity<ID> auditable, boolean isNew) {

		if (null == auditorAware) {
			return null;
		}

		T auditor = auditorAware.getCurrentAuditor();
		String auditorId = auditorAware.getCurrentAuditorId();

		if (isNew) {
			auditable.setCreatedBy(auditorId);
		}

		auditable.setLastModifiedBy(auditorId);

		return auditor;
	}

	/**
	 * Touches the auditable regarding modification and creation date. Creation date is only set on new auditables.
	 * 
	 * @param auditable
	 * @return
	 */
	private DateTime touchDate(final AuditablePoEntity<ID> auditable, boolean isNew) {

		DateTime now = new DateTime();

		if (isNew) {
			auditable.setCreatedDate(now);
		}

		auditable.setLastModifiedDate(now);

		return now;
	}

}
